﻿/*
@class: TabIndexManger Class
@author: Darren D. Benston
@created: 25 jun 2007
@last modified: 28 jun 2007
*/
/*
// CHANGE LOG
	mod 09 jul 2007 - ddb
	ver 1.05
		added:
			new method; focusFirstObject(); used to focus the first item in tab_array
			
	mod 01 jul 2007 - ddb
	ver 1.04
		added:
			new method; focusObject()
		changed:
			addObjects(); new params; resetFocus_bool:Boolean (sets focus on first object in tab_array), focusLastObject_bool:Boolean (sets focus on the last object in tab_array)

	mod 29 jun 2007 - ddb
	ver 1.03
		changed:
			added scope parameter to constructor, (only for error handling popup)
			added error handling if TextField does not have a styleSheet defined
			added variable/movieclip deletion/clean up, old values were persisting
		
	mod 28 jun 2007 - ddb
	ver 1.02
		changed:
			renamed 'add()' to 'addObjects()' as it was throwing an error, (reserved keyword)
		added:
			new method; 'addTextFieldLinks()'
			(requires 'import com.xfactorstudio.xml.xpath.*;' )
				*	based on code by Michael A. Jordan [michael@majordan.net]
				*	http://www.majordan.net/test/flashlinks/flashlinks.zip
	
*/
/*

// NOTES:

//	** MovieClip's must have an eventHandler defined in order to receive focus/be tabbable...
//	** if MovieClip is disabled ("clip.enabled = false"), tabIndex/focus will not work/is also disabled

//	• General HTML Tag syntax
		- all tags must close;
			RIGHT:"<br/>", "<img .../>", "<tab/>" (whitespace is not required; "<br />" and "<br/>" both valid)
			!! WRONG:"<br>", "<img ...>", "<tab>" !!
	  
//	• Anchor/Link Tag syntax (<a...>click me</a>);
		- tag name IS case sensitive; both opening and closing tags must be lowercase
			RIGHT:"<a ...>click me</a>"
			!! WRONG:"<A ...>click me</A>", !!
		- attributes can be in any order (attributes like href, target, id, class can be in any order)
			RIGHT:"<a href="#" target="_blank" class="defaultLink">click me</a>"
			RIGHT:"<a target="_blank" class="defaultLink" href="#">click me</a>"
		- attributes are NOT case sensitive
			RIGHT:"<a HREF="#">click me</a>"
			RIGHT:"<a href="#">click me</a>"
			
// USAGE TIPS:
	
//	• for global nav/persistant objects, assign high index number like 1000+,
	  resetting only the lower/temp objects, leaving global index's out of normal range
		// global nav tabIndex
		nextGlobalNavTabIndex_num = 1000;
		SCOPE_obj.introBtn_mc.tabIndex = nextGlobalNavTabIndex_num;
		nextGlobalNavTabIndex_num++;
	
//	• to prevent user from tabbing out of flash tab_array (onto browser or other html objects)
		// tab loopBack
		// prevents user from tabbing out of the flash object and onto other HTML/Browser tabbable objects
		tabLooperIndex_num = 2000;
		var tabLooper:MovieClip = SCOPE_obj.createEmptyMovieClip("tabLooper",SCOPE_obj.getNextHighestDepth());
		tabLooper.tabMgr = tabMgr;
		tabLooper.tabIndex = tabLooperIndex_num;
		tabLooper.onSetFocus = function(p_oldFocus_obj:Object, p_newFocus_obj:Object):Void{
			//trace("tabLooper.onSetFocus()");
			if (p_newFocus_obj._name == this._name){
				this.tabMgr.focusFirstObject();
			}
		}
		Selection.addListener(tabLooper);
		
//	• new screen/state
		//
		// tabMgr
		tabMgr.reset();
		tabMgr.addObjects([SCOPE_obj.monitor_mc.inbox_mc.email0_mc, SCOPE_obj.monitor_mc.inbox_mc.email1_mc, SCOPE_obj.monitor_mc.inbox_mc.email2_mc, SCOPE_obj.monitor_mc.inbox_mc.email3_mc]);
	
//	• for popups 
		- onShowPopup(){
			// tabMgr
			// create restore point
			var myHistoryIndex_num:Number = tabMgr.save();
			// clear old tabIndex's
			tabMgr.reset();
			// set new tabIndex's
			tabMgr.addObjects([SCOPE_obj.splashPanel_mc.panel_mc.introPanelBtn_mc,SCOPE_obj.splashPanel_mc.panel_mc.body_txt]);
		  }
		- onHidePopup(){
			// revert to restore point
			var myRevertSuccess_bool:Boolean = tabMgr.revert(myHistoryIndex_num);
		  }
	
//	• for textfields with <a href> links
		* must assign stylesheet before assigning htmlText
			var tabMgr:com.utils.TabIndexManager;
			var myStyles_css:TextField.StyleSheet;
			//
			// tabMgr
			tabMgr = new com.utils.TabIndexManager();
			// tab-enabled textfield links requires use of css
			myStyles_css = new TextField.StyleSheet();
			myStyles_css.onLoad = function(success):Void{if (!success) com.utils.PopUp.Alert({text:"<b>**CSS Error**</b>\nError opening URL \""+SCOPE_obj.shell.myXML.attributes.styleSheetURL+"\"",root:SCOPE_obj, textWidth:300, isBlocker:false})};
			myStyles_css.load(myXML.attributes.styleSheetURL);
			//
			SCOPE_obj.anchor.body_txt.styleSheet = myStyles_css;
			SCOPE_obj.anchor.body_txt.htmlText = myText;
			tabMgr.addObjects([SCOPE_obj.anchor.body_txt]);

		
		
METHOD TABLE:
public function TabIndexManager(p_scope_obj:MovieClip){
public function addObjects(p_objects_array:Array, resetFocus_bool:Boolean, focusLastObject_bool:Boolean):Number {
private function focusObject(p_tabObject_obj:Object):Boolean{
private function addTextFieldLinks(p_textfield:TextField, p_nextTabIndex_num:Number, p_focusedStyle_str:String, p_visitedStyle_str:String):Void {
public function remove(p_tabbable_obj:Object):Void{
public function save():Number{
public function revert(p_saveIndex_num):Boolean{
public function reset():Void{
public function removeHistory(p_saveIndex_num):Boolean{
public function resetHistory():Void{
private function toString():String {
*/
import com.xfactorstudio.xml.xpath.*;
import com.utils.PopUp;
class com.utils.TabIndexManager2 {
	private var SCOPE_obj:Object;
	private var className_str:String;
	private var version_str:String;
	private var nextTabIndex_num:Number;
	private var tab_array:Array;
	private var history_array:Array;
	private var tabHolder_obj:Object;
	//
	// constructor function
	public function TabIndexManager2(p_scope_obj:Object) {
		// scope is only for PopUp.Alert error handling, optional
		SCOPE_obj = (p_scope_obj == undefined) ? _level0 : p_scope_obj;
		className_str = "TabIndexManager2";
		version_str = "v1.05";
		trace(this + " (" + this.version_str + ")");
		// init
		reset();
		resetHistory();
	}
	public function addObjects(p_objects_array:Array, p_targetFocus_str:String):Number {
		//trace("addObjects()");
		//trace("  targetFocus:"+p_targetFocus_str);
		var p_len:Number = p_objects_array.length;
		for (var i:Number = 0; i < p_len; i++) {
			var o = p_objects_array[i];
			if (o instanceof TextField) {
				//trace("  "+o._name+" is TextField");
				addTextFieldLinks(o)
			} else {
				tab_array.push(o);
				//trace("  " + o._name + ":" + nextTabIndex_num);
				//o.tabEnabled = true
				o.tabIndex = nextTabIndex_num;
				nextTabIndex_num++;
			}
		}
		// set focus
		if (p_targetFocus_str.toLowerCase() == "first") {
			// select first object in tab_array
			focusObject(p_objects_array[0]);
		} else if (p_targetFocus_str.toLowerCase() == "last") {
			// select last object in tab_array
			focusObject(p_objects_array[(p_len-1)]);
		}
		//trace("  tab_array.length:"+tab_array.length)
		//trace("  return:"+nextTabIndex_num);
		// 
		Accessibility.updateProperties();
		return nextTabIndex_num
	}
	public function remove(p_tabbable_obj:Object):Void {
		//trace("remove(" + p_tabbable_obj._name + ")");
		if (p_tabbable_obj == Selection.getFocus()) {
			// change focus before removing object
			Selection.setFocus();
		}
		var len:Number = tab_array.length;
		for (var i:Number = 0; i < len; i++) {
			var o:Object = tab_array[i];
			if (o == p_tabbable_obj) {
				o.tabIndex = undefined;
				tab_array.splice(i, 1);
				break;
			}
		}
		//trace("  tab_array.length:"+tab_array.length);		
	}
	public function save():Number {
		//trace("save()");
		var nextIndex_num:Number = history_array.length;
		history_array[nextIndex_num] = tab_array.slice();
		//trace("  return:"+nextIndex_num);
		//trace("  history_array.length:" + history_array.length);
		return nextIndex_num;
	}
	public function revert(p_saveIndex_num):Boolean {
		//trace("revert(" + p_saveIndex_num + ")");
		//trace("  p_saveIndex_num:"+p_saveIndex_num);
		var success_bool:Boolean = (history_array[p_saveIndex_num] != undefined) ? true : false;
		//trace("  return:" + success_bool);
		this.reset();
		this.addObjects(history_array[p_saveIndex_num]);
		return success_bool;
	}
	public function reset():Void {
		//trace("reset()");
		// set focus to tabHolder before removing objects
		if (tabHolder_obj != undefined) {
			focusTabHolder();
		}
		// does not alter histor_array
		nextTabIndex_num = 1;
		var len:Number = tab_array.length;
		for (var i:Number = 0; i < len; i++) {
			var o:Object = tab_array[i];
			o.tabIndex = undefined;
		}
		tab_array = new Array();
		//trace("  tab_array.length:" + tab_array.length);
	}
	public function focusObject(p_tabObject_obj:Object):Boolean{
		//trace("focusObject()");
		var o:Object = p_tabObject_obj;
		//trace("  o:"+o);
		// select specified object to reset tab-order to top-of-the-order
		var success_bool:Boolean = Selection.setFocus(o);
		// clear selection in case object is textfield
		if (o instanceof TextField) {
			//trace("  "+o._name+" is TextField");
			Selection.setSelection(0, 0);
		}
		//trace("    return:"+success_bool);
		return success_bool;
	}
	public function focusFirstObject():Boolean{
		//trace("focusFirstObject()");
		// select specified object to reset tab-order to top-of-the-order
		var o:Object = tab_array[0];
		var success_bool:Boolean = focusObject(o);
		//trace("  return:"+success_bool);
		return success_bool;
	}
	
	public function focusTabHolder():Boolean{
		//trace("focusTabHolder()");
		// set focus to tabHolder before removing objects
		var o:Object = tabHolder_obj;
		var success_bool = focusObject(o);
		//trace("  return:"+success_bool);
		return success_bool;
	}

	public function setTabHolder(p_tabHolder_obj:Object):Boolean{
		//trace("setTabHolder()");
		var success_bool = (p_tabHolder_obj == undefined) ? false : true;
		tabHolder_obj = p_tabHolder_obj;
		//trace("  tabHolder_obj:"+tabHolder_obj);
		//trace("  return:"+success_bool);
		return success_bool;
	}
	public function removeHistory(p_saveIndex_num):Boolean {
		//trace("removeHistory()");
		//trace("  p_saveIndex_num:"+p_saveIndex_num);
		//trace("  old history_array.length:" + history_array.length);
		var success_bool:Boolean = (history_array[p_saveIndex_num] != undefined) ? true : false;
		history_array.splice(p_saveIndex_num, 1);
		//trace("  new history_array.length:" + history_array.length);
		//trace("  return:"+success_bool);
		return success_bool;
	}
	public function resetHistory():Void {
		//trace("resetHistory()");
		// does not alter tabIndexing
		history_array = new Array();
		//trace("  history_array.length:" + history_array.length);
	}
	private function addTextFieldLinks(p_textfield:TextField, p_nextTabIndex_num:Number, p_focusedStyle_str:String, p_visitedStyle_str:String):Void {
		//trace("  addTextFieldLinks()");
		// textfield MUST have a styleSheet defined for this to work
		if (p_textfield.styleSheet == undefined) {
			// fail
			com.utils.PopUp.Alert({text:"<b>**CSS Error**</b>\nTextField.styleSheet is undefined. TextFields must have a styleSheet for tab-enabled links\n("+p_textfield+")\n"+this,root:SCOPE_obj, textWidth:300, isBlocker:false})
			return;
		}
		var parentClip_mc:MovieClip = p_textfield._parent;
		var tfOriginalWidth_num:Number = p_textfield._width;
		//trace("    tfOriginalWidth_num1:"+tfOriginalWidth_num);
		p_textfield.html = true;
		p_textfield.visitedStyle = false;
		p_textfield.p_focusedStyle_str = (!p_focusedStyle_str) ? "focused" : p_focusedStyle_str;
		p_textfield.p_visitedStyle_str = (!p_visitedStyle_str) ? "visited" : p_visitedStyle_str;
		var styles = p_textfield.styleSheet.getStyleNames();
		for (var i in styles) {
			if (styles[i] == "." + p_textfield.p_visitedStyle_str) {
				p_textfield.visitedStyle = true;
				break;
			}
		}
		// clean up any objects from prior usage
		delete p_textfield.xml;
		delete p_textfield.linkNodes_arr;
		delete p_textfield.links_arr;
		removeMovieClip(parentClip_mc["proxy_" + p_textfield._name + "_mc"]);
		// convert textField htmlText into an XML object  
		p_textfield.xml = new XML();
		p_textfield.xml.ignoreWhite = true;
		p_textfield.xml.parseXML(p_textfield.htmlText);
		// use XPath to select the anchor link nodes
		p_textfield.linkNodes_arr = XPath.selectNodes(p_textfield.xml, "//a");//<a href>here</a>
		var len = p_textfield.linkNodes_arr.length;
		//trace("  linkNodes_arr:" + p_textfield.linkNodes_arr);
		trace("  linkNodes_arr.length:" + len);
		//trace("** p_textfield.linkNodes_arr:"+p_textfield.linkNodes_arr);
		if (len > 0) {
			// break string into arrays of text; pre-anchor text, anchor text, post anchor text
			// remove string-anchor tags from text (to be replaced with xml-anchor tags)
			// remove closing anchor tag
			var tfText_arr = p_textfield.htmlText.split("</a>");
			for (var i = 0; i < tfText_arr.length; i++) {
				// remove opening anchor tag
				tfText_arr[i] = tfText_arr[i].substring(0, tfText_arr[i].indexOf("<a href", 0));
				// insert space between (fixed set of) punctuation and closing tag
				tfText_arr[i] = tfText_arr[i].split(".</").join(". </");
				tfText_arr[i] = tfText_arr[i].split(",</").join(", </");
				tfText_arr[i] = tfText_arr[i].split(":</").join(": </");
				tfText_arr[i] = tfText_arr[i].split("!</").join("! </");
			}
			for (var i = 0; i < len; i++) {
				// insert xml-anchor tags where string-anchor tags were
				tfText_arr.splice(i * 2 + 1, 0, p_textfield.linkNodes_arr[i]);
			}
			//trace("** tfText_arr:"+tfText_arr);
			// container for html proxies for this textField
			p_textfield.proxy_mc = parentClip_mc.createEmptyMovieClip("proxy_" + p_textfield._name + "_mc", parentClip_mc.getNextHighestDepth());
			p_textfield.proxy_mc.tabEnabled = false;
			p_textfield.proxy_mc.tabChildren = true;
			p_textfield.proxy_mc._x = p_textfield._x;
			p_textfield.proxy_mc._y = p_textfield._y;
			// define an array to contain the link objects
			p_textfield.links_arr = new Array();
			for (var i = 0; i < tfText_arr.length; i++) {
				//trace("  tfText_arr["+i+"]:"+tfText_arr[i]);
				if (typeof tfText_arr[i] == "string") {
					//trace("  typeof == 'string'");
					// non-anchor string, use as-is
					p_textfield.proxy_mc.createTextField("textNode" + i + "_txt", i + 1, 0, 0, 1, 1);
					var textProxy = p_textfield.proxy_mc["textNode" + i + "_txt"];
					textProxy.styleSheet = p_textfield.styleSheet;
					textProxy.html = true;
					textProxy.htmlText = tfText_arr[i].toString();
					textProxy.tabEnabled = false;
					textProxy._focusrect = true;
					textProxy._alpha = 0;
					textProxy.autoSize = false;
					textProxy.wordWrap = false;
					textProxy.multiline = false;
				} else {
					//trace(" typeof != 'string'");
					// xml-anchor tag
					var linkObj = p_textfield.links_arr[p_textfield.links_arr.length] = new Object();
					linkObj.tag = tfText_arr[i];
					linkObj.parent = p_textfield;
					// get the link description as text and not as html
					p_textfield.proxy_mc.createTextField("temp_txt", i + 1, 0, 0, 1, 1);
					p_textfield.proxy_mc.temp_txt.html = true;
					p_textfield.proxy_mc.temp_txt.htmlText = linkObj.tag.toString();
					linkObj.description = p_textfield.proxy_mc.temp_txt.text;
					// link attributes
					linkObj.href = (!linkObj.tag.attributes["href"]) ? null : linkObj.tag.attributes["href"];
					linkObj.target = (!linkObj.tag.attributes["target"]) ? null : linkObj.tag.attributes["target"];
					linkObj.initClass = (!linkObj.tag.attributes["class"]) ? null : linkObj.tag.attributes["class"];
					// make a movieClip
					linkObj.button = p_textfield.proxy_mc.createEmptyMovieClip("link" + i + "_mc", i + 1);
					linkObj.button.parent = linkObj;
					linkObj.button.visited = false;
					linkObj.button._x = 0;
					linkObj.button._y = 0;
					// some content 
					linkObj.button.lineStyle(0, 0x000000, 0);
					linkObj.button.moveTo(0, 0);
					linkObj.button.lineTo(p_textfield._width, 0);
					linkObj.button.lineTo(p_textfield._width, p_textfield._height);
					linkObj.button.lineTo(0, p_textfield._height);
					linkObj.button.lineTo(0, 0);
					// tab and focus properties
					linkObj.button._focusrect = true;
					linkObj.button.focusEnabled = true;
					linkObj.button.tabEnabled = true;
					// add to tabMgr.tab_array via tabMgr.addObjects()
					addObjects([linkObj.button]);
					//
					linkObj.button._accProps = new Object();
					linkObj.button._accProps.name = linkObj.button.parent.description;
					linkObj.button.useHandCursor = false;
					// onSetFocus changes className to "focused" for the link and sets up a key listener
					linkObj.button.onSetFocus = function() {
						this._focusrect = true;
						// use .focused css class style
						this.parent.tag.attributes["class"] = this.parent.parent.p_focusedStyle_str;
						this.parent.parent.htmlText = this.parent.parent.xml.toString();
						if (!this.keyListener) {
							this.keyListener = new Object();
						}
						this.keyListener.parent = this;
						this.keyListener.onKeyDown = function() {
							// on Key.ENTER or Key.SPACE execute getURL
							if (Key.getCode() == Key.ENTER || Key.getCode() == Key.SPACE) {
								if (this.parent.parent.href && !Accessibility.isActive()) {
									trace("\n  TextField <a> link activated (" + this._parent+")");
									//trace("getURL HREF: " + this.parent.parent.href);
									if (this.parent.parent.target) {
										getURL(this.parent.parent.href, this.parent.parent.target);
									} else {
										getURL(this.parent.parent.href);
									}
									this.parent.visited = true;
									this.parent.parent.tag.attributes["class"] = (this.parent.parent.parent.visitedStyle && this.parent.visited == true) ? this.parent.parent.parent.p_visitedStyle_str : this.parent.parent.initClass;
									this.parent.parent.htmlText = this.parent.parent.xml;
								}
							}
						};
						Key.addListener(this.keyListener);
					};
					// to make sure screen reader treats link as a button
					linkObj.button.onRelease = function() {
						if (this.parent.href && Accessibility.isActive()) {
							trace("\n  TextField <a> link activated (" + this+")");
							//trace("getURL HREF: " + this.parent.href);
							if (this.parent.target) {
								getURL(this.parent.href, this.parent.target);
							} else {
								getURL(this.parent.href);
							}
							this.visited = true;
							this.onKillFocus();
						}
					};
					// onKillFocus function changes class attribute on given link and removes the Key event listener  
					linkObj.button.onKillFocus = function() {
						// use .visited css stylr
						this.parent.tag.attributes["class"] = (this.parent.parent.visitedStyle && this.visited == true) ? "visited" : this.parent.initClass;
						this.parent.parent.htmlText = this.parent.parent.xml;
						Key.removeListener(this.keyListener);
					};
				}
			}
			//trace("  tfOriginalWidth_num2:"+tfOriginalWidth_num);
			//p_textfield.proxy_mc._width = p_textfield._width = tfOriginalWidth_num;
			if (!p_textfield._accProps) {
				p_textfield._accProps = new Object();
			}
			p_textfield._accProps.silent = true;
			Accessibility.updateProperties();
		}
	}
	private function toString():String {
		return className_str;
	}
}
